/* 
   Socket.cpp

   Copyright (C) 2002-2004 Ren� Nyffenegger

   This source code is provided 'as-is', without any express or implied
   warranty. In no event will the author be held liable for any damages
   arising from the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this source code must not be misrepresented; you must not
   claim that you wrote the original source code. If you use this source code
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original source code.

   3. This notice may not be removed or altered from any source distribution.

   Ren� Nyffenegger rene.nyffenegger@adp-gmbh.ch

   poulet24 2009 - Source modified for Popup. Bugs may have been introduced.
 */

#include <stdio.h>
#include <iostream>
#include <PopupOSAL.hpp>

// In case we use non blocking socket, and we receive
// a WSAWOULDBLOCK error, we'll sleep this amount of
// milliseconds before retrying to recv/send data
#define SLEEP_TIME_BEFORE_NEXT_TRY_MS 50

using namespace std;
using namespace PopupUtils;

int Socket::nbSockets = 0;

Socket *Socket::newServerSocket(int p_port, int p_nbMaxConnections,
                                string *p_errMsg)
{
  if (nbSockets == 0) {
    WSADATA info;
    if (WSAStartup(MAKEWORD(2,0), &info)) {
      if (p_errMsg) {
        *p_errMsg = "Could not start WSA";
      }
      return 0;
    }
  }

  sockaddr_in sa;
  memset(&sa, 0, sizeof(sa));

  sa.sin_family = PF_INET;
  sa.sin_port = htons(p_port);

  OSSocketFd _s = socket(AF_INET, SOCK_STREAM, 0);
  if (_s == INVALID_SOCKET)
  {
    if (p_errMsg) {
      *p_errMsg = strerror(errno);
    }
    return 0;
  }

  /* bind the socket to the internet address */
  if (bind(_s, (sockaddr *)&sa, sizeof(sockaddr_in)) == SOCKET_ERROR) {
    closesocket(_s);
    if (p_errMsg) {
      *p_errMsg = strerror(errno);
    }
    return 0;
  }
  else
  {
    listen(_s, p_nbMaxConnections);
    return new Socket(_s);
  }
}

Socket *Socket::newClientSocket(const std::string  & p_host, int p_port,
                                string *p_errMsg, Socket::Mode p_mode)
{
  if (nbSockets == 0) {
    WSADATA info;
    if (WSAStartup(MAKEWORD(2,0), &info)) {
      if (p_errMsg) {
        *p_errMsg = "Could not start WSA";
      }
      return 0;
    }
  }

  OSSocketFd _s = socket(AF_INET, SOCK_STREAM, 0);
  if (_s == INVALID_SOCKET)
  {
    if (p_errMsg) {
      *p_errMsg = strerror(errno);
    }
    return 0;
  }

  struct hostent *he = gethostbyname(p_host.c_str());

  if (he == 0) {
    if (p_errMsg) {
      *p_errMsg = "Cannot resolve " + p_host + " server IP address.";
    }
    closesocket(_s);
    return 0;
  }

  sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(p_port);
  addr.sin_addr = *((in_addr *)he->h_addr);
  memset(&(addr.sin_zero), 0, 8);

  if (::connect(_s, (sockaddr *) &addr, sizeof(sockaddr))) {
    if (p_errMsg) {
      *p_errMsg = strerror(errno);
    }
    closesocket(_s);
    return 0;
  }

  if(p_mode == NonBlockingSocket) {
    u_long arg = 1;
    ioctlsocket(_s, FIONBIO, &arg);
  }

  return new Socket(_s);
}

Socket::Socket(OSSocketFd s)
: s(s), refCounter(new int(1)), keepaliveHelper(0), keepaliveEnabled(false)
{
  ++nbSockets;
};

Socket::~Socket() {
  if (! --(*refCounter)) {
    close();
    delete refCounter;
  }

  --nbSockets;
  if (!nbSockets) WSACleanup();
}

Socket::Socket(const Socket& o)
: s(o.s), refCounter(o.refCounter)
{
  (*refCounter)++;
  nbSockets++;
}

Socket& Socket::operator=(Socket& o) {
  (*o.refCounter)++;

  refCounter=o.refCounter;
  s         =o.s;
  nbSockets++;

  return *this;
}

void Socket::close() {
  closesocket(s);
}


void Socket::resetKeepalive()
{
  keepaliveCountDownMs = keepaliveConfigTimeoutMs;
  keepalivePacketSent  = false;
}

bool Socket::handleKeepalive(size_t p_sleepTimeMs)
{
  bool _rc = true;

  if (keepaliveEnabled == true)
  {
    // Update the time spent, waiting for packets
    keepaliveCountDownMs -= p_sleepTimeMs;

    // Deadline! Something must be wrong!
    if (keepaliveCountDownMs <= 0)
    {
      _rc = false;
    }
    // If we have only 2 seconds remaining before timeout
    // is reached, let's send a ping request
    else if ((keepaliveCountDownMs <= 2000) &&
             (keepalivePacketSent == false))
    {
      // Send a probe!
      if (keepaliveHelper != 0) {
        keepalivePacketSent = keepaliveHelper->sendProbe();
      }
    }
  }

  return _rc;
}

bool Socket::sendBytes(const unsigned char *p_buff, size_t p_size)
{
  bool  _rc = true;
  size_t _remainingBytes = p_size;
  int _nbBytesSent = 0;

  const char *_ptr = (const char*) p_buff;

  while (_rc && _remainingBytes > 0)
  {
    _nbBytesSent = send (s, _ptr, _remainingBytes, 0);

    if (_nbBytesSent == SOCKET_ERROR)
    {
      if (WSAGetLastError() == WSAEWOULDBLOCK)
      {
        // That's OK!... Let's wait a little bit before next retry
        Sleep(SLEEP_TIME_BEFORE_NEXT_TRY_MS);
      }
      else
      {
        error("Socket send() error");
        _rc = false;
      }
    }
    // Socket closed on the other end
    else if (_nbBytesSent == 0)
    {
      error("Socket closed by foreign host");
      _rc = false;
    }
    // Success
    else
    {
      _remainingBytes -= _nbBytesSent;
      _ptr = _ptr + _nbBytesSent;
    }
  }

  return _rc;
}

bool Socket::receiveBytes(char *p_buff, size_t p_size = 0)
{
  bool  _rc = true;
  size_t _remainingBytes = p_size;
  int _nbBytesRead = 0;
  char *_ptr = p_buff;

  while (_rc && _remainingBytes > 0)
  {
    _nbBytesRead = recv(s, (char*) _ptr, _remainingBytes, 0);

    // Socket error
    if (_nbBytesRead == SOCKET_ERROR)
    {
      int _err = WSAGetLastError();
      if (_err == WSAEWOULDBLOCK) // Failed simply because of non-blocking status
      {
        // Sleep a little bit
        Sleep(SLEEP_TIME_BEFORE_NEXT_TRY_MS);
        // Make sure we're not disconnected
        _rc = handleKeepalive(SLEEP_TIME_BEFORE_NEXT_TRY_MS);
        if (!_rc) error("Keepalive detected connection error");
      }
      else  // Fatal socket error (wire disconnected?)
      {
        error("Socket recv() error");
        _rc = false;
      }
    }
    // Socket closed on the other end
    else if (_nbBytesRead == 0)
    {
      error("Socket closed by foreign host");
      _rc = false;
    }
    // Success
    else
    {
      resetKeepalive();
      _remainingBytes -= _nbBytesRead;
      _ptr = _ptr + _nbBytesRead;
    }
  }
  return _rc;
}

bool Socket::enableKeepAlive(bool p_doEnable, int p_timeout,
                             KeepAliveHelper *p_helper)
{
  if (p_doEnable)
  {
    if (p_helper == 0) {
      error("Cannot enable keepalive on windows without a valid helper!");
      return false;
    } else {
      keepaliveHelper = p_helper;
      // Mezmerize keepalive params
      keepaliveEnabled = true;
      keepaliveConfigTimeoutMs = (p_timeout * 1000);
      resetKeepalive();
      // Switch socket to non blocking mode
      u_long arg = 1;
      return (ioctlsocket(s, FIONBIO, &arg) == 0);
    }
  }
  else
  {
    keepaliveEnabled = false;
    u_long arg = 0;
    return (ioctlsocket(s, FIONBIO, &arg) == 0);
  }
}

Socket* Socket::accept(string * p_errMsg)
{
  OSSocketFd new_sock = ::accept(s, 0, 0);
  if (new_sock == INVALID_SOCKET) {
    if (p_errMsg != 0) {
      *p_errMsg = "Invalid Socket";
    }
    return 0;
  }
  else
  {
    return new Socket(new_sock);
  }
}

